Hasta ahora hemos visto que si bien CUDA no es un lenguaje imposible de aprender, puede llegar a ser un dolor de cabeza el tener muchos apuntadores y manejar la memoria de un modo tan rudimentario.
Sin embargo hay alternativas que nos permiten trabajar en entornos más agradables, un ejemplo de ellos es PyCUDA creado con Andreas Klöckner. Básicamente PyCUDA se encarga de mapear todo CUDA dentro de Python.
Por poner un ejemplo, un código simple sería el siguiente
In [1]:
import pycuda.autoinit
import pycuda.driver as drv
import numpy
from pycuda.compiler import SourceModule
mod = SourceModule("""
__global__ void multiplicar(float *dest, float *a, float *b)
{
const int i = threadIdx.x;
dest[i] = a[i] * b[i];
}
""")
multiplicar = mod.get_function("multiplicar")
a = numpy.random.randn(400).astype(numpy.float32)
b = numpy.random.randn(400).astype(numpy.float32)
dest = numpy.zeros_like(a)
print dest
multiplicar(
drv.Out(dest), drv.In(a), drv.In(b),
block=(400,1,1), grid=(1,1))
print dest
print dest-a*b
Al correr este programa vamos a obtener un montón de ceros; algo no muy interesante. Sin embargo detrás de escenas sí pasó algo interesante.
Útil ¿cierto?
In [2]:
import pycuda.driver as cuda
import pycuda.autoinit
from pycuda.compiler import SourceModule
In [3]:
import numpy
a = numpy.random.randn(4,4)
sin embargo nuestro arreglo a
consiste en números de doble precisión, dado que no todos los GPU de NVIDIA cuentan con doble precisión es que hacemos lo siguiente
In [4]:
a = a.astype(numpy.float32)
finalmente, necesitmos un arreglo hacia el cuál transferir la información, así que deberíamos guardar la memoria en el dispositivo:
In [5]:
a_gpu = cuda.mem_alloc(a.nbytes)
como último paso, necesitamos tranferir los datos al GPU
In [6]:
cuda.memcpy_htod(a_gpu, a)
In [7]:
mod = SourceModule("""
__global__ void duplicar(float *a)
{
int idx = threadIdx.x + threadIdx.y*4;
a[idx] *= 2;
}
""")
Si no hay errores, el código ahora ha sido compilado y cargado en el dispositivo. Encontramos una referencia a nuestra pycuda.driver.Function
y la llamamos, especificando a_gpu
como el argumento, y un tamaño de bloque de $4\times 4$:
In [8]:
mod
Out[8]:
In [9]:
func = mod.get_function("duplicar")
func(a_gpu, block=(4,4,1))
In [10]:
func
Out[10]:
Finalmente recogemos la información del GPU y la mostramos con el a
original
In [11]:
a_duplicado = numpy.empty_like(a)
cuda.memcpy_dtoh(a_duplicado, a_gpu)
print a_duplicado
print a
In [12]:
print(type(a))
print(type(a_gpu))
print(type(a_duplicado))
In [13]:
a_duplicado
Out[13]:
¡Y eso es todo! Hemos terminado con el trabajo. PyCUDA se encarga de hacer toda la limpieza por nosotros.